package apple.core;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.Collection;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.border.Border;

import org.apache.log4j.Logger;

import apple.debug.Disassembler;
import apple.debug.LocatableEventRequest;
import apple.debug.ProgramLine;
import apple.debug.StackFrame;

public class Debugger extends JComponent {

   static Logger logger = Logger.getLogger(Debugger.class);

   private List<StackFrame> frames;

   private Collection<LocatableEventRequest> breakpointRequests;

   private MOS6502Debug processor;

   private JPanel stackPanel;

   private JTextField pc = new JTextField("0000");

   private JTextField sp = new JTextField("00");

   private JTextField x = new JTextField("00");

   private JTextField y = new JTextField("00");

   private JTextField a = new JTextField("00");

   private JTextField flags = new JTextField("00100000");

   private JTextArea textArea;

   public Debugger(MOS6502Debug processor) {
      this.processor = processor;
      processor.setDebugger(this);
      frames = processor.frames();
      breakpointRequests = processor.breakpointRequests();
      // processor.createBreakpointRequest((short) 0xFA62);
      setLayout(new BorderLayout());
      Dimension size = new Dimension(600, 450);
      setPreferredSize(size);
      Component controlPanel = createControlPanel();
      add(controlPanel, BorderLayout.PAGE_START);
      Component breakpointPanel = createBreakpointPanel();
      add(breakpointPanel, BorderLayout.LINE_START);
      Component memoryPanel = createMemoryPanel();
      add(memoryPanel, BorderLayout.CENTER);
      Component stackPanel = createStackPanel();
      add(stackPanel, BorderLayout.LINE_END);
      Component statusPanel = createStatusPanel();
      add(statusPanel, BorderLayout.PAGE_END);
   }

   public void suspend() {
      refreshMemoryPanel(processor.programCounter);
      refreshStackFramePanel();
      refreshStatusPanel();
      revalidate();
   }

   private JPanel createControlPanel() {
      JPanel buttonPanel = new JPanel(new GridLayout(1, 0, 5, 0));
      final JButton suspendButton = new JButton("Suspend");
      final JButton resumeButton = new JButton("Resume");
      final JButton stepNextButton = new JButton("Step Next");
      final JButton stepOverButton = new JButton("Step Over");
      final JButton stepReturnButton = new JButton("Step Return");
      ActionListener listener = new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            Object source = e.getSource();
            if (source == suspendButton) {
               processor.suspend();
            } else if (source == resumeButton) {
               processor.resume();
            } else if (source == stepNextButton) {
               processor.stepNext();
            } else if (source == stepOverButton) {
               processor.stepOver();
            } else if (source == stepReturnButton) {
               processor.stepReturn();
            }
         }
      };
      suspendButton.addActionListener(listener);
      resumeButton.addActionListener(listener);
      stepNextButton.addActionListener(listener);
      stepOverButton.addActionListener(listener);
      stepReturnButton.addActionListener(listener);
      buttonPanel.add(suspendButton);
      buttonPanel.add(resumeButton);
      buttonPanel.add(stepNextButton);
      buttonPanel.add(stepOverButton);
      buttonPanel.add(stepReturnButton);
      JPanel panel = new JPanel();
      panel.add(buttonPanel);
      return panel;
   }

   private JPanel createBreakpointPanel() {
      JPanel panel = new JPanel(new GridBagLayout());
      GridBagConstraints c = new GridBagConstraints();
      c.gridx = 0;
      c.gridy = 0;
      c.gridwidth = 2;
      panel.add(new JLabel("Breakpoints"), c);
      c.gridwidth = 1;
      for (final LocatableEventRequest breakpoint : breakpointRequests) {
         JButton button = new JButton(String.format("%04X", breakpoint
               .location()));
         final JCheckBox checkbox = new JCheckBox();
         checkbox.setSelected(breakpoint.isEnabled());
         ItemListener list = new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
               breakpoint.setEnabled(checkbox.isSelected());
            }
         };
         checkbox.addItemListener(list);
         c.gridy++;
         c.gridx = 0;
         panel.add(checkbox, c);
         c.gridx = 1;
         panel.add(button, c);
      }
      return panel;
   }

   private JPanel createMemoryPanel() {
      JPanel panel = new JPanel();
      JPanel radioPanel = new JPanel();
      radioPanel.setLayout(new BoxLayout(radioPanel, BoxLayout.PAGE_AXIS));
      JLabel label = new JLabel("View as:");
      radioPanel.add(label);
      JRadioButton codeButton = new JRadioButton("Code", true);
      JRadioButton dataButton = new JRadioButton("Data", false);
      ButtonGroup group = new ButtonGroup();
      group.add(codeButton);
      group.add(dataButton);
      radioPanel.add(codeButton);
      radioPanel.add(dataButton);
      panel.add(radioPanel);
      textArea = new JTextArea();
      textArea.setFont(Font.decode("Monospaced"));
      textArea.setEditable(false);
      JScrollPane scrollPane = new JScrollPane(textArea);
      scrollPane.setPreferredSize(new Dimension(200, 200));
      panel.add(scrollPane);
      return panel;
   }

   private void refreshMemoryPanel(short address) {
      textArea.setText(null);
      // page (= 256 bytes) + 2 bytes possible overflow for two-operand
      // instruction on last byte = 258 bytes maximum necessary
      short pageAddress = (short) (address & 0xFF00);
      byte[] byteArray = processor.memory.read(pageAddress, 258);
      List<ProgramLine> lines = Disassembler
            .disassemble(byteArray, pageAddress);
      for (ProgramLine line : lines) {
         textArea.append(line.getProgramLine() + "\n");
      }
   }

   private Component createStackPanel() {
      Border raisedbevel = BorderFactory.createRaisedBevelBorder();
      Border loweredbevel = BorderFactory.createLoweredBevelBorder();
      Border compound = BorderFactory.createCompoundBorder(raisedbevel,
            loweredbevel);
      compound = BorderFactory.createTitledBorder(compound, "Stack");
      stackPanel = new JPanel(new GridBagLayout());
      JScrollPane stackScrollPane = new JScrollPane(stackPanel);
      stackScrollPane.setPreferredSize(new Dimension(175, 400));
      stackScrollPane.setViewportBorder(BorderFactory.createEmptyBorder(10, 10,
            10, 10));
      return stackScrollPane;
   }

   private void refreshStackFramePanel() {
      stackPanel.removeAll();
      GridBagConstraints c = new GridBagConstraints();
      c.ipadx = 5;
      c.ipady = 5;
      // stackPanel.add(new JLabel("Stack"));
      int sp = processor.stackPointer & 0xFF;
      for (int i = 0xFF; i >= sp; i--) {
         short stackAddress = Utilities.getWord(MOS6502.STACK_PAGE, (byte) i);
         c.gridy = 0xFF - i;
         c.gridx = 0;
         stackPanel.add(new JLabel(String.format("%04X: ", stackAddress)), c);
         c.gridx = 1;
         stackPanel.add(new JLabel(String.format("%02X", processor.memory
               .read(stackAddress))), c);
      }
      c.gridx = 2;
      for (StackFrame frame : frames) {
         int framesp = frame.getStackPointer() & 0xFF;
         short entryAddress = frame.getEntryAddress();
         JButton button = new JButton(String.format("%04X", entryAddress));
         c.gridy = 0xFF - framesp;
         stackPanel.add(button, c);
      }
   }

   private JPanel createStatusPanel() {
      JPanel gridPanel = new JPanel(new GridLayout(2, 0, 5, 0));
      gridPanel.add(new JLabel("PC"));
      gridPanel.add(new JLabel("SP"));
      gridPanel.add(new JLabel("X"));
      gridPanel.add(new JLabel("Y"));
      gridPanel.add(new JLabel("A"));
      gridPanel.add(new JLabel("NV1BDIZC"));
      pc = new JTextField("0000");
      sp = new JTextField("00");
      x = new JTextField("00");
      y = new JTextField("00");
      a = new JTextField("00");
      flags = new JTextField("00100000");
      gridPanel.add(pc);
      gridPanel.add(sp);
      gridPanel.add(x);
      gridPanel.add(y);
      gridPanel.add(a);
      gridPanel.add(flags);
      JPanel panel = new JPanel();
      panel.add(gridPanel);
      return panel;
   }

   private void refreshStatusPanel() {
      pc.setText(String.format("%04X", processor.programCounter));
      sp.setText(String.format("%02X", processor.stackPointer));
      x.setText(String.format("%02X", processor.indexRegisterX));
      y.setText(String.format("%02X", processor.indexRegisterY));
      a.setText(String.format("%02X", processor.accumulator));
      int statusByte = processor.processorStatus.getByte() & 0xFF;
      flags.setText(Integer.toBinaryString(statusByte));
   }
}
